#include "FireSystem.h"
#include "IslandApp.h"
#include "Camera.h"
#include <fstream>

FireSystem::FireSystem() :  bHasNewRay(false), emitter_index(0), mEditMode(false), bDelete(false), 
						vb_emitters(0), vb_particles0(0), vb_particles1(0), mHeatThreshold(100.0f), 
						bFirstRun(false), bOnFire(false), tex_emitters(0), srv_emitters(0), rtv_emitters(0),						
						mFxEmitParticles(0), mFxUpdateParticles(0), mFxDrawParticles(0), 
						mFxUpdateHeat(0), mFxProceduralEmitters(0), mLayout_Emitter(0), 
						mLayout_FireParticle(0), tex_heat(0), srv_heat(0), srv_noise(0), m_particleAge(1.0f),
						bFadeOut(false), m_srvFireParticle(0) {}			

FireSystem::~FireSystem()
{
	if (g_dev)
		g_dev->Release();
	if (g_ctx)
		g_ctx->Release();
	if (g_layout)
		g_layout->Release();
	if (tex_emitters)
		tex_emitters->Release();
	if (srv_emitters)
		srv_emitters->Release();
	if (rtv_emitters)
		rtv_emitters->Release();
	if (mFxEmitParticles)
		mFxEmitParticles->Release();
	if (mFxUpdateParticles)
		mFxUpdateParticles->Release();
	if (mFxDrawParticles)
		mFxDrawParticles->Release();
	if (mFxUpdateHeat)
		mFxUpdateHeat->Release();
	if (mFxProceduralEmitters)
		mFxProceduralEmitters->Release();
	if (vb_particles0)
		vb_particles0->Release();
	if (vb_particles1)
		vb_particles1->Release();
	if (mLayout_Emitter)
		mLayout_Emitter->Release();
	if (mLayout_FireParticle)
		mLayout_FireParticle->Release();
	if (tex_heat)
		tex_heat->Release();
	if (srv_heat)
		srv_heat->Release();
	if (srv_noise)
		srv_noise->Release();
	if (m_srvFireParticle)
		m_srvFireParticle->Release();

}


void FireSystem::initBuffers()
{
	
	
	////////////////////////
	D3D11_TEXTURE2D_DESC tdesc;
	tdesc.Width = (int)sqrt((float)MAX_EMITTERS);
	tdesc.Height = (int)sqrt((float)MAX_EMITTERS);
	tdesc.MipLevels = 1;
	tdesc.ArraySize = 1;
	tdesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
	tdesc.CPUAccessFlags = 0;
	tdesc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT;
	tdesc.MiscFlags = 0;
	tdesc.SampleDesc.Count = 1;
	tdesc.SampleDesc.Quality = 0;
	tdesc.Usage = D3D11_USAGE_DEFAULT;

	HRESULT hr = g_dev->CreateTexture2D(&tdesc, 0, &tex_heat);
	if (FAILED(hr))
		MessageBoxA(0, "Error creating heat texture", 0, 0);
	

	//_____ build heat texture shader resource view___________
	D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc;
	srv_desc.Format = tdesc.Format;
	srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
	srv_desc.Texture2D.MipLevels = 1;
	srv_desc.Texture2D.MostDetailedMip = 0;


	hr = g_dev->CreateShaderResourceView(tex_heat, &srv_desc, &srv_heat);
	if (FAILED(hr))
		MessageBoxA(0, "Error creating heat srv", 0, 0);
}

void FireSystem::init(ID3D11Device* device, ID3D11DeviceContext* context, ID3D11InputLayout* ia)
{
	g_dev = device;
	g_ctx = context;
	g_layout = ia;

	g_dev->AddRef();
	g_ctx->AddRef();
	g_layout->AddRef();

	initBuffers();
	

	HRESULT hr = D3DX11CreateShaderResourceViewFromFile(g_dev, L"../Desert_Island/media/noise_texture.jpg", 0, 0, &srv_noise, 0);
	if (FAILED(hr)) MessageBoxA(0, "Error creating noise texture srv", 0, 0);

	createEffect("../Desert_Island/shaders/emitParticles.fxo", &mFxEmitParticles);
	createEffect("../Desert_Island/shaders/updateParticles.fxo", &mFxUpdateParticles);
	createEffect("../Desert_Island/shaders/drawParticles.fxo", &mFxDrawParticles);
	createEffect("../Desert_Island/shaders/proceduralEmitters.fxo", &mFxProceduralEmitters);

	createLayouts();


	hr = D3DX11CreateShaderResourceViewFromFileA(g_dev, "../Desert_Island/media/flare0.dds", 0, 0, &m_srvFireParticle, 0);
	if (FAILED(hr)) MessageBoxA(0, "Error creating fire particle srv", 0, 0);


}

void FireSystem::buildEmittersBuffer()
{
	// EMITTERS BUFFER

	mNumEmitters = emitter_index;

	//-----------------------------------------------------------
	if (vb_particles0)
		vb_particles0->Release();
	if (vb_particles1)
		vb_particles1->Release();

	// particles buffer : vertex buffer + stream output
	D3D11_BUFFER_DESC vb_desc;
	ZeroMemory(&vb_desc, sizeof(vb_desc));
	vb_desc.Usage = D3D11_USAGE_DEFAULT;
	vb_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT;
	vb_desc.ByteWidth = sizeof(FireParticle) * MAX_PARTICLES;
	
	vb_desc.MiscFlags = vb_desc.StructureByteStride = 0;

	D3D11_SUBRESOURCE_DATA vbdata;
	vbdata.SysMemPitch = vbdata.SysMemSlicePitch = 0;
	vbdata.pSysMem = 0; //initial_data;

	// create
	HRESULT hr = g_dev->CreateBuffer(&vb_desc, 0, &vb_particles0);
	if (FAILED(hr))
		MessageBoxA(0, "Error creating particles0 buffer", 0, 0);

	hr = g_dev->CreateBuffer(&vb_desc, 0, &vb_particles1);
	if (FAILED(hr))
		MessageBoxA(0, "Error creating particles1 buffer", 0, 0);
}

void FireSystem::setEditMode(bool v)
{ 
	if (v == false)
	{
		// (re-)create the emitters buffer
		generateProcEmitters();
	}
	
	mEditMode = v; 
}

void FireSystem::passUpdateParticles(float dt)
{
	static float time = 0.0f;
	time += dt; // dt 

	if (bFadeOut)
		m_particleAge -= dt * 0.2f;

	if (m_particleAge < 0.0f)
		bOnFire = false;


	float h = 1.4f + (time/10.0f) * (4.0f - 1.4f);

	// set the particles buffer as the vertex buffer
	UINT stride = sizeof(FireParticle);
	UINT offset = 0;

	//// device state
	g_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
	g_ctx->IASetInputLayout(mLayout_FireParticle);

	// vertex buffer
	g_ctx->IASetVertexBuffers(0, 1, &vb_particles0, &stride, &offset);

	//// set the particles buffer as the stream-out buffer
	UINT so_offset = 0; 
	g_ctx->SOSetTargets(1, &vb_particles1, &so_offset);


	DXApp::setFloatVar(mFxUpdateParticles, val, "fVal");

	//// shader variables
	ID3DX11EffectScalarVariable* fx_dt = mFxUpdateParticles->GetVariableByName("dt")->AsScalar();
	fx_dt->SetFloat(dt);
	// shader variables
	ID3DX11EffectShaderResourceVariable* fx_tex_heat = mFxUpdateParticles->GetVariableByName("tex_heat")->AsShaderResource();
	fx_tex_heat->SetResource(srv_heat);
	//__________________________________________________________________
	ID3DX11EffectShaderResourceVariable* fx_tex_noise = mFxUpdateParticles->GetVariableByName("tex_noise")->AsShaderResource();
	fx_tex_noise->SetResource(srv_noise);
	//__________________________________________________________________
	ID3DX11EffectScalarVariable* fx_activate_emitter = mFxUpdateParticles->GetVariableByName("activate_emitter")->AsScalar();
	fx_activate_emitter->SetInt(0); // first emitter always active

	DXApp::setIntVar(mFxUpdateParticles, MAX_EMITTERS, "iMaxEmitters");
	DXApp::setBoolVar(mFxUpdateParticles, true, "bUseDiskDistribution");
	//__________________________________________________________________
	ID3DX11EffectScalarVariable* fx_heatTex_dimension = mFxUpdateParticles->GetVariableByName("heatTex_dimension")->AsScalar();
	fx_heatTex_dimension->SetInt((int)(sqrt((float)MAX_EMITTERS)));
	//__________________________________________________________________
	ID3DX11EffectScalarVariable* fx_heat_threshold = mFxUpdateParticles->GetVariableByName("heat_threshold")->AsScalar();
	fx_heat_threshold->SetFloat(mHeatThreshold);
	//__________________________________________________________________
	ID3DX11EffectScalarVariable* fx_time = mFxUpdateParticles->GetVariableByName("time")->AsScalar();
	fx_time->SetFloat(time);
	//__________________________________________________________________
	ID3DX11EffectScalarVariable* fx_HeightThreshold = mFxUpdateParticles->GetVariableByName("fHeightThreshold")->AsScalar();
	fx_HeightThreshold->SetFloat(h);
	//__________________________________________________________________
	DXApp::setVectorVar(mFxUpdateParticles, (void*)&m_windDirection, "vWindDir");
	DXApp::setFloatVar(mFxUpdateParticles, m_windPower, "fWindPower");
	DXApp::setBoolVar(mFxUpdateParticles, bOnFire, "bActive");
	DXApp::setFloatVar(mFxUpdateParticles, m_particleAge, "fParticleAge");

	// Matrices
	UINT id = 0;
	ID3DX11EffectMatrixVariable* fx_matrices=0;
	for (UINT i=0; i<objects.size(); i++) {
		for (UINT j=0; j<objects[i]->m_numSubsets; j++) {
			fx_matrices = mFxUpdateParticles->GetVariableByName("matrices")->GetElement(id)->AsMatrix();
			
			fx_matrices->SetMatrix((float*)&(objects[i]->getSubsetMatrix(j)));
			id++;
		}
	}


#define _stats
#ifdef _stats
	D3D11_QUERY_DESC queryDesc;
	queryDesc.Query = D3D11_QUERY_SO_STATISTICS;
	queryDesc.MiscFlags = 0;

	ID3D11Query* pQuery;
	g_dev->CreateQuery(&queryDesc, &pQuery);
	g_ctx->Begin(pQuery);
#endif

	//// execute the shader
	ID3DX11EffectTechnique* fx_tech = mFxUpdateParticles->GetTechniqueByName("UpdateParticles");
	fx_tech->GetPassByIndex(0)->Apply(0, g_ctx);
	
	if (bFirstRun) {
		// for procedurally generated emitters DrawAuto() should be used..
		g_ctx->DrawAuto();
		bFirstRun = false;
	}
	else
		g_ctx->DrawAuto();
	

#ifdef _stats
	g_ctx->End(pQuery);
	
	while( S_FALSE == g_ctx->GetData(pQuery, NULL, 0, 0) )
	{
	}

	D3D11_QUERY_DATA_SO_STATISTICS so_data;
	so_data.NumPrimitivesWritten = so_data.PrimitivesStorageNeeded = 0;
	HRESULT hr = g_ctx->GetData(pQuery, &so_data, sizeof(so_data), 0);

	pQuery->Release();
#endif





	if (so_data.PrimitivesStorageNeeded > so_data.NumPrimitivesWritten)
	{
		OutputDebugStringA("---------------- Fire Particles Buffer Overflow ------------------");
	}

	ID3D11Buffer* buff_empty[1] = { 0 };
	g_ctx->SOSetTargets(1, buff_empty, &offset);
}

void FireSystem::passDrawParticles()
{

	// set the particles buffer as the vertex buffer
	UINT stride = sizeof(FireParticle);
	UINT offset = 0;
	g_ctx->IASetVertexBuffers(0, 1, &vb_particles1, &stride, &offset);

	// device state
	g_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
	g_ctx->IASetInputLayout(mLayout_FireParticle);

	// shader variables
	ID3DX11EffectMatrixVariable* fx_mView = mFxDrawParticles->GetVariableByName("mView")->AsMatrix();
	XMMATRIX view = mApp->getCamera()->getViewMatrix();
	fx_mView->SetMatrix((float*)&view);
	//__________________________________________________________________
	ID3DX11EffectMatrixVariable* fx_mProj = mFxDrawParticles->GetVariableByName("mProj")->AsMatrix();
	XMMATRIX proj = mApp->getCamera()->getProjectionMatrix();
	fx_mProj->SetMatrix((float*)&proj);

	DXApp::setSrvVar(mFxDrawParticles, m_srvFireParticle, "texFireParticle");

#ifdef _stats
	D3D11_QUERY_DESC queryDesc;
	queryDesc.Query = D3D11_QUERY_PIPELINE_STATISTICS;
	queryDesc.MiscFlags = 0;

	ID3D11Query* pQuery;
	g_dev->CreateQuery(&queryDesc, &pQuery);
	g_ctx->Begin(pQuery);
#endif

	// execute the shader
	ID3DX11EffectTechnique* fx_tech = mFxDrawParticles->GetTechniqueByName("DrawParticles");
	fx_tech->GetPassByIndex(0)->Apply(0, g_ctx);
	g_ctx->DrawAuto();

	
#ifdef _stats
	g_ctx->End(pQuery);
	
	while( S_FALSE == g_ctx->GetData(pQuery, NULL, 0, 0) )
	{
	}

	D3D11_QUERY_DATA_PIPELINE_STATISTICS so_data;
	HRESULT hr = g_ctx->GetData(pQuery, &so_data, sizeof(so_data), 0);

	pQuery->Release();
#endif


	
}

void FireSystem::passUpdateHeat()
{
	

	
	// swap - this should at the end of the last pass
	ID3D11Buffer* tmp;
	tmp = vb_particles0;
	vb_particles0 = vb_particles1;
	vb_particles1 = tmp;
}

void FireSystem::createLayouts()
{
	D3D11_INPUT_ELEMENT_DESC layout[] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT,	0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "DIRECTION", 0,  DXGI_FORMAT_R32G32B32A32_FLOAT,	0, 16, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "ID", 0,			DXGI_FORMAT_R32_UINT,			0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "ACTIVE", 0,  DXGI_FORMAT_R8_UINT		,			0, 36, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "PARAMS", 0,  DXGI_FORMAT_R32G32_FLOAT,			0, 40, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };

	D3DX11_PASS_DESC passDesc;
	ID3DX11EffectTechnique* tech;
	tech = mFxEmitParticles->GetTechniqueByName("EmitParticles");
	tech->GetPassByIndex(0)->GetDesc(&passDesc);

	HRESULT hr = g_dev->CreateInputLayout(layout, 5, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, &mLayout_Emitter);
	if (FAILED(hr))
		MessageBoxA(0, "Error creating emitter layout", 0, 0);

	///////////////////
	D3D11_INPUT_ELEMENT_DESC particle_layout[] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT,	0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "DIRECTION", 0,  DXGI_FORMAT_R32G32B32A32_FLOAT,	0, 16, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "PARAMETERS", 0,  DXGI_FORMAT_R32G32B32A32_FLOAT,	0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };

	tech = mFxUpdateParticles->GetTechniqueByName("UpdateParticles");
	tech->GetPassByIndex(0)->GetDesc(&passDesc);

	hr = g_dev->CreateInputLayout(particle_layout, 3, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, &mLayout_FireParticle);
	if (FAILED(hr))
		MessageBoxA(0, "Error creating fireParticle layout", 0, 0);


	
}

bool FireSystem::loadEffect(const char* filename, std::vector<char> &compiledShader) const
{

	std::ifstream fin(filename, std::ios::binary);
	if (!fin)
		return false;
	fin.seekg(0, std::ios_base::end);
	int size = (int)fin.tellg();

	compiledShader.resize(size);
	fin.seekg(0, std::ios_base::beg);
	fin.read(&compiledShader[0], size);
	if (!fin)
		return false;
	return true;

}

void FireSystem::createEffect(const char* filename, ID3DX11Effect** _fx)
{
	std::vector<char> compiledShader(0);
	
	// shaders
	if (!loadEffect(filename, compiledShader))
		MessageBoxA(0, "Error Loading Effect", 0, 0);
	
	HRESULT hr = D3DX11CreateEffectFromMemory(&compiledShader[0], compiledShader.size(), 0, g_dev, _fx);
	if (FAILED(hr)) MessageBoxA(0, "Error creating FX", 0, 0);
	

}

void FireSystem::run(float dt)
{
	if (!bOnFire)
		return;

	passUpdateParticles(dt);
	passDrawParticles();
	passUpdateHeat();
}

void FireSystem::onFire(bool v)
{
	if (bOnFire == false && v == true) {
		bFirstRun = true;
		bFadeOut = false;
		generateProcEmitters();
		m_particleAge = 1.0f;
	}

	bOnFire = true;
}


void FireSystem::generateProcEmitters()
{
	static bool bGenerated = false;
	if (bGenerated) return;

	buildEmittersBuffer();

	// input: all registered objects
	g_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	g_ctx->IASetInputLayout(g_layout);

	UINT offset = -1;
	g_ctx->SOSetTargets(1, &vb_particles0, &offset);	

	
	DXApp::setSrvVar(mFxProceduralEmitters, srv_noise, "texNoise");
	DXApp::setFloatVar(mFxProceduralEmitters, m_minTriangleArea, "fMinTriangleArea");
	DXApp::setFloatVar(mFxProceduralEmitters, m_maxTriangleArea, "fMaxTriangleArea");
	DXApp::setIntVar(mFxProceduralEmitters, m_minTriSamples, "iMinSamples");
	DXApp::setIntVar(mFxProceduralEmitters, m_maxTriSamples, "iMaxSamples");


	UINT globalID = 0;

	// i starts for 1, i.e. generate emitters only for the tree object
	for (unsigned int i=0; i<objects.size(); ++i)
	{		
		DXApp::setIntVar(mFxProceduralEmitters, globalID, "iObjectId");

		for (UINT j=0; j<objects[i]->m_numSubsets; j++)
			objects[i]->generateEmitters(j, globalID++, mFxProceduralEmitters, "ProceduralEmitters");
	}

	ID3D11Buffer* buffer_empty[1] = {0};
	g_ctx->SOSetTargets(1, buffer_empty, &offset);

	// ID Pass
	
	// Set Input Topology, Input Layout
	g_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
	g_ctx->IASetInputLayout(mLayout_FireParticle);

	// Set vertex buffer
	UINT stride = sizeof(FireParticle);
	offset = 0;
	g_ctx->IASetVertexBuffers(0, 1, &vb_particles0, &stride, &offset);

	// Set Stream-Out buffer
	g_ctx->SOSetTargets(1, &vb_particles1, &offset);	

	// Draw
	ID3DX11EffectTechnique* tech = mFxProceduralEmitters->GetTechniqueByName("EmmitersID");
	tech->GetPassByIndex(0)->Apply(0, g_ctx);
	g_ctx->DrawAuto();
	
	g_ctx->SOSetTargets(1, buffer_empty, &offset);

	//// swap - 
	ID3D11Buffer* tmp;
	tmp = vb_particles0;
	vb_particles0 = vb_particles1;
	vb_particles1 = tmp;

	bGenerated = true;
}


void FireSystem::setWindVector(float v[])
{
	// normalize windDir, and make it 2D (y=0)
	XMFLOAT3 windDir = XMFLOAT3(v[0], 0, v[1]);
	XMVECTOR dir = XMLoadFloat3(&windDir);
	dir = XMVector3Normalize(dir);
	XMStoreFloat3(&m_windDirection, dir);
}


void FireSystem::stopFire()
{
	bFadeOut = true;
}

void FireSystem::addObject(FireObject* m)
{ 
	for (UINT i=0; i<objects.size(); i++) {
		if (objects[i] == m)
			return;
	}
	objects.push_back(m); 
}